/*
 * Copyright 2013 serso aka se.solovyev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Contact details
 *
 * Email: se.solovyev@gmail.com
 * Site:  http://se.solovyev.org
 */

// This is a OpenGL ES 1.0 dynamic font rendering system. It loads actual font
// files, generates a font map (texture) from them, and allows rendering of
// text strings.
//
// NOTE: the rendering portions of this class uses a sprite batcher in order
// provide decent speed rendering. Also, rendering assumes a BOTTOM-LEFT
// origin, and the (x,y) positions are relative to that, as well as the
// bottom-left of the string to render.

package org.solovyev.android.calculator.plot;

import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.opengl.GLUtils;

import javax.microedition.khronos.opengles.GL10;

public class GLText {

	//--Constants--//
	public final static int CHAR_START = 32;        // First Character (ASCII Code)
	public final static int CHAR_END = 126;            // Last Character (ASCII Code)
	public final static int CHAR_CNT = (((CHAR_END - CHAR_START) + 1) + 1);  // Character Count (Including Character to use for Unknown)

	public final static int CHAR_NONE = 32;            // Character to Use for Unknown (ASCII Code)
	public final static int CHAR_UNKNOWN = (CHAR_CNT - 1);  // Index of the Unknown Character

	public final static int FONT_SIZE_MIN = 6;        // Minumum Font Size (Pixels)
	public final static int FONT_SIZE_MAX = 180;    // Maximum Font Size (Pixels)

	public final static int CHAR_BATCH_SIZE = 100;    // Number of Characters to Render Per Batch

	//--Members--//
	GL10 gl;                                        // GL10 Instance
	AssetManager assets;                            // Asset Manager
	SpriteBatch batch;                                // Batch Renderer

	int fontPadX, fontPadY;                            // Font Padding (Pixels; On Each Side, ie. Doubled on Both X+Y Axis)

	float fontHeight;                                // Font Height (Actual; Pixels)
	float fontAscent;                                // Font Ascent (Above Baseline; Pixels)
	float fontDescent;                                // Font Descent (Below Baseline; Pixels)

	int textureId;                                    // Font Texture ID [NOTE: Public for Testing Purposes Only!]
	int textureSize;                                // Texture Size for Font (Square) [NOTE: Public for Testing Purposes Only!]
	TextureRegion textureRgn;                        // Full Texture Region

	float charWidthMax;                                // Character Width (Maximum; Pixels)
	float charHeight;                                // Character Height (Maximum; Pixels)
	final float[] charWidths;                        // Width of Each Character (Actual; Pixels)
	TextureRegion[] charRgn;                        // Region of Each Character (Texture Coordinates)
	int cellWidth, cellHeight;                        // Character Cell Width/Height
	int rowCnt, colCnt;                                // Number of Rows/Columns

	float scaleX, scaleY;                            // Font Scale (X,Y Axis)
	float spaceX;                                    // Additional (X,Y Axis) Spacing (Unscaled)


	//--Constructor--//
	// D: save GL instance + asset manager, create arrays, and initialize the members
	// A: gl - OpenGL ES 10 Instance
	public GLText(GL10 gl, AssetManager assets) {
		this.gl = gl;                                // Save the GL10 Instance
		this.assets = assets;                        // Save the Asset Manager Instance

		batch = new SpriteBatch(gl, CHAR_BATCH_SIZE);  // Create Sprite Batch (with Defined Size)

		charWidths = new float[CHAR_CNT];            // Create the Array of Character Widths
		charRgn = new TextureRegion[CHAR_CNT];        // Create the Array of Character Regions

		// initialize remaining members
		fontPadX = 0;
		fontPadY = 0;

		fontHeight = 0.0f;
		fontAscent = 0.0f;
		fontDescent = 0.0f;

		textureId = -1;
		textureSize = 0;

		charWidthMax = 0;
		charHeight = 0;

		cellWidth = 0;
		cellHeight = 0;
		rowCnt = 0;
		colCnt = 0;

		scaleX = 1.0f;                                // Default Scale = 1 (Unscaled)
		scaleY = 1.0f;                                // Default Scale = 1 (Unscaled)
		spaceX = 0.0f;
	}

	//--Load Font--//
	// description
	//    this will load the specified font file, create a texture for the defined
	//    character range, and setup all required values used to render with it.
	// arguments:
	//    file - Filename of the font (.ttf, .otf) to use. In 'Assets' folder.
	//    size - Requested pixel size of font (height)
	//    padX, padY - Extra padding per character (X+Y Axis); to prevent overlapping characters.
	public boolean load(String file, int size, int padX, int padY) {

		// setup requested values
		fontPadX = padX;                                // Set Requested X Axis Padding
		fontPadY = padY;                                // Set Requested Y Axis Padding

		// load the font and setup paint instance for drawing
		Typeface tf = Typeface.createFromAsset(assets, file);  // Create the Typeface from Font File
		Paint paint = new Paint();                    // Create Android Paint Instance
		paint.setAntiAlias(true);                    // Enable Anti Alias
		paint.setTextSize(size);                    // Set Text Size
		paint.setColor(0xffffffff);                // Set ARGB (White, Opaque)
		paint.setTypeface(tf);                        // Set Typeface

		// get font metrics
		Paint.FontMetrics fm = paint.getFontMetrics();  // Get Font Metrics
		fontHeight = (float) Math.ceil(Math.abs(fm.bottom) + Math.abs(fm.top));  // Calculate Font Height
		fontAscent = (float) Math.ceil(Math.abs(fm.ascent));  // Save Font Ascent
		fontDescent = (float) Math.ceil(Math.abs(fm.descent));  // Save Font Descent

		// determine the width of each character (including unknown character)
		// also determine the maximum character width
		char[] s = new char[2];                        // Create Character Array
		charWidthMax = charHeight = 0;                // Reset Character Width/Height Maximums
		float[] w = new float[2];                    // Working Width Value
		int cnt = 0;                                    // Array Counter
		for (char c = CHAR_START; c <= CHAR_END; c++) {  // FOR Each Character
			s[0] = c;                                    // Set Character
			paint.getTextWidths(s, 0, 1, w);        // Get Character Bounds
			charWidths[cnt] = w[0];                    // Get Width
			if (charWidths[cnt] > charWidthMax)        // IF Width Larger Than Max Width
				charWidthMax = charWidths[cnt];        // Save New Max Width
			cnt++;                                    // Advance Array Counter
		}
		s[0] = CHAR_NONE;                            // Set Unknown Character
		paint.getTextWidths(s, 0, 1, w);            // Get Character Bounds
		charWidths[cnt] = w[0];                        // Get Width
		if (charWidths[cnt] > charWidthMax)        // IF Width Larger Than Max Width
			charWidthMax = charWidths[cnt];            // Save New Max Width
		cnt++;                                        // Advance Array Counter

		// set character height to font height
		charHeight = fontHeight;                        // Set Character Height

		// find the maximum size, validate, and setup cell sizes
		cellWidth = (int) charWidthMax + (2 * fontPadX);  // Set Cell Width
		cellHeight = (int) charHeight + (2 * fontPadY);  // Set Cell Height
		int maxSize = cellWidth > cellHeight ? cellWidth : cellHeight;  // Save Max Size (Width/Height)
		if (maxSize < FONT_SIZE_MIN || maxSize > FONT_SIZE_MAX)  // IF Maximum Size Outside Valid Bounds
			return false;                                // Return Error

		// set texture size based on max font size (width or height)
		// NOTE: these values are fixed, based on the defined characters. when
		// changing start/end characters (CHAR_START/CHAR_END) this will need adjustment too!
		if (maxSize <= 24)                            // IF Max Size is 18 or Less
			textureSize = 256;                        // Set 256 Texture Size
		else if (maxSize <= 40)                    // ELSE IF Max Size is 40 or Less
			textureSize = 512;                        // Set 512 Texture Size
		else if (maxSize <= 80)                    // ELSE IF Max Size is 80 or Less
			textureSize = 1024;                        // Set 1024 Texture Size
		else                                            // ELSE IF Max Size is Larger Than 80 (and Less than FONT_SIZE_MAX)
			textureSize = 2048;                        // Set 2048 Texture Size

		// create an empty bitmap (alpha only)
		Bitmap bitmap = Bitmap.createBitmap(textureSize, textureSize, Bitmap.Config.ALPHA_8);  // Create Bitmap
		Canvas canvas = new Canvas(bitmap);        // Create Canvas for Rendering to Bitmap
		bitmap.eraseColor(0x00000000);                // Set Transparent Background (ARGB)

		// calculate rows/columns
		// NOTE: while not required for anything, these may be useful to have :)
		colCnt = textureSize / cellWidth;            // Calculate Number of Columns
		rowCnt = (int) Math.ceil((float) CHAR_CNT / (float) colCnt);  // Calculate Number of Rows

		// render each of the characters to the canvas (ie. build the font map)
		float x = fontPadX;                            // Set Start Position (X)
		float y = (cellHeight - 1) - fontDescent - fontPadY;  // Set Start Position (Y)
		for (char c = CHAR_START; c <= CHAR_END; c++) {  // FOR Each Character
			s[0] = c;                                    // Set Character to Draw
			canvas.drawText(s, 0, 1, x, y, paint);    // Draw Character
			x += cellWidth;                            // Move to Next Character
			if ((x + cellWidth - fontPadX) > textureSize) {  // IF End of Line Reached
				x = fontPadX;                            // Set X for New Row
				y += cellHeight;                        // Move Down a Row
			}
		}
		s[0] = CHAR_NONE;                            // Set Character to Use for NONE
		canvas.drawText(s, 0, 1, x, y, paint);        // Draw Character

		// generate a new texture
		int[] textureIds = new int[1];                // Array to Get Texture Id
		gl.glGenTextures(1, textureIds, 0);        // Generate New Texture
		textureId = textureIds[0];                    // Save Texture Id

		// setup filters for texture
		gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);  // Bind Texture
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);  // Set Minification Filter
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);  // Set Magnification Filter
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);  // Set U Wrapping
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);  // Set V Wrapping

		// load the generated bitmap onto the texture
		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);  // Load Bitmap to Texture
		gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);    // Unbind Texture

		// release the bitmap
		bitmap.recycle();                            // Release the Bitmap

		// setup the array of character texture regions
		x = 0;                                        // Initialize X
		y = 0;                                        // Initialize Y
		for (int c = 0; c < CHAR_CNT; c++) {        // FOR Each Character (On Texture)
			charRgn[c] = new TextureRegion(textureSize, textureSize, x, y, cellWidth - 1, cellHeight - 1);  // Create Region for Character
			x += cellWidth;                            // Move to Next Char (Cell)
			if (x + cellWidth > textureSize) {
				x = 0;                                    // Reset X Position to Start
				y += cellHeight;                        // Move to Next Row (Cell)
			}
		}

		// create full texture region
		textureRgn = new TextureRegion(textureSize, textureSize, 0, 0, textureSize, textureSize);  // Create Full Texture Region

		// return success
		return true;                                    // Return Success
	}

	//--Begin/End Text Drawing--//
	// D: call these methods before/after (respectively all draw() calls using a text instance
	//    NOTE: color is set on a per-batch basis, and fonts should be 8-bit alpha only!!!
	// A: red, green, blue - RGB values for font (default = 1.0)
	//    alpha - optional alpha value for font (default = 1.0)
	// R: [none]
	public void begin() {
		begin(1.0f, 1.0f, 1.0f, 1.0f);                // Begin with White Opaque
	}

	public void begin(float alpha) {
		begin(1.0f, 1.0f, 1.0f, alpha);            // Begin with White (Explicit Alpha)
	}

	public void begin(float red, float green, float blue, float alpha) {
		gl.glColor4f(red, green, blue, alpha);        // Set Color+Alpha
		gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);  // Bind the Texture
		batch.beginBatch();                            // Begin Batch
	}

	public void end() {
		batch.endBatch();                            // End Batch
		gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);        // Restore Default Color/Alpha
	}

	//--Draw Text--//
	// D: draw text at the specified x,y position
	// A: text - the string to draw
	//    x, y - the x,y position to draw text at (bottom left of text; including descent)
	// R: [none]
	public void draw(String text, float x, float y) {
		float chrHeight = cellHeight * scaleY;        // Calculate Scaled Character Height
		float chrWidth = cellWidth * scaleX;            // Calculate Scaled Character Width
		int len = text.length();                        // Get String Length
		x += (chrWidth / 2.0f) - (fontPadX * scaleX);  // Adjust Start X
		y += (chrHeight / 2.0f) - (fontPadY * scaleY);  // Adjust Start Y
		for (int i = 0; i < len; i++) {            // FOR Each Character in String
			int c = (int) text.charAt(i) - CHAR_START;  // Calculate Character Index (Offset by First Char in Font)
			if (c < 0 || c >= CHAR_CNT)                // IF Character Not In Font
				c = CHAR_UNKNOWN;                        // Set to Unknown Character Index
			batch.drawSprite(x, y, chrWidth, chrHeight, charRgn[c]);  // Draw the Character
			x += (charWidths[c] + spaceX) * scaleX;    // Advance X Position by Scaled Character Width
		}
	}

	//--Draw Text Centered--//
	// D: draw text CENTERED at the specified x,y position
	// A: text - the string to draw
	//    x, y - the x,y position to draw text at (bottom left of text)
	// R: the total width of the text that was drawn
	public float drawC(String text, float x, float y) {
		float len = getLength(text);                // Get Text Length
		draw(text, x - (len / 2.0f), y - (getCharHeight() / 2.0f));  // Draw Text Centered
		return len;                                    // Return Length
	}

	public float drawCX(String text, float x, float y) {
		float len = getLength(text);                // Get Text Length
		draw(text, x - (len / 2.0f), y);            // Draw Text Centered (X-Axis Only)
		return len;                                    // Return Length
	}

	public void drawCY(String text, float x, float y) {
		draw(text, x, y - (getCharHeight() / 2.0f));  // Draw Text Centered (Y-Axis Only)
	}

	//--Set Scale--//
	// D: set the scaling to use for the font
	// A: scale - uniform scale for both x and y axis scaling
	//    sx, sy - separate x and y axis scaling factors
	// R: [none]
	public void setScale(float scale) {
		scaleX = scaleY = scale;                        // Set Uniform Scale
	}

	public void setScale(float sx, float sy) {
		scaleX = sx;                                    // Set X Scale
		scaleY = sy;                                    // Set Y Scale
	}

	//--Get Scale--//
	// D: get the current scaling used for the font
	// A: [none]
	// R: the x/y scale currently used for scale
	public float getScaleX() {
		return scaleX;                                // Return X Scale
	}

	public float getScaleY() {
		return scaleY;                                // Return Y Scale
	}

	//--Set Space--//
	// D: set the spacing (unscaled; ie. pixel size) to use for the font
	// A: space - space for x axis spacing
	// R: [none]
	public void setSpace(float space) {
		spaceX = space;                                // Set Space
	}

	//--Get Space--//
	// D: get the current spacing used for the font
	// A: [none]
	// R: the x/y space currently used for scale
	public float getSpace() {
		return spaceX;                                // Return X Space
	}

	//--Get Length of a String--//
	// D: return the length of the specified string if rendered using current settings
	// A: text - the string to get length for
	// R: the length of the specified string (pixels)
	public float getLength(String text) {
		float len = 0.0f;                            // Working Length
		int strLen = text.length();                    // Get String Length (Characters)
		for (int i = 0; i < strLen; i++) {        // For Each Character in String (Except Last
			int c = (int) text.charAt(i) - CHAR_START;  // Calculate Character Index (Offset by First Char in Font)
			len += (charWidths[c] * scaleX);        // Add Scaled Character Width to Total Length
		}
		len += (strLen > 1 ? ((strLen - 1) * spaceX) * scaleX : 0);  // Add Space Length
		return len;                                    // Return Total Length
	}

	//--Get Width/Height of Character--//
	// D: return the scaled width/height of a character, or max character width
	//    NOTE: since all characters are the same height, no character index is required!
	//    NOTE: excludes spacing!!
	// A: chr - the character to get width for
	// R: the requested character size (scaled)
	public float getCharWidth(char chr) {
		int c = chr - CHAR_START;                    // Calculate Character Index (Offset by First Char in Font)
		return (charWidths[c] * scaleX);            // Return Scaled Character Width
	}

	public float getCharWidthMax() {
		return (charWidthMax * scaleX);            // Return Scaled Max Character Width
	}

	public float getCharHeight() {
		return (charHeight * scaleY);                // Return Scaled Character Height
	}

	//--Get Font Metrics--//
	// D: return the specified (scaled) font metric
	// A: [none]
	// R: the requested font metric (scaled)
	public float getAscent() {
		return (fontAscent * scaleY);                // Return Font Ascent
	}

	public float getDescent() {
		return (fontDescent * scaleY);                // Return Font Descent
	}

	public float getHeight() {
		return (fontHeight * scaleY);                // Return Font Height (Actual)
	}

	//--Draw Font Texture--//
	// D: draw the entire font texture (NOTE: for testing purposes only)
	// A: width, height - the width and height of the area to draw to. this is used
	//    to draw the texture to the top-left corner.
	public void drawTexture(int width, int height) {
		batch.beginBatch(textureId);                // Begin Batch (Bind Texture)
		batch.drawSprite(textureSize / 2, height - (textureSize / 2), textureSize, textureSize, textureRgn);  // Draw
		batch.endBatch();                            // End Batch
	}


}
